package servent;

import java.io.File;
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.util.Enumeration;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.JFileChooser;

import net.jxta.discovery.DiscoveryService;
import net.jxta.document.Advertisement;
import net.jxta.document.AdvertisementFactory;
import net.jxta.id.IDFactory;
import net.jxta.peergroup.PeerGroup;
import net.jxta.pipe.PipeID;
import net.jxta.pipe.PipeService;
import net.jxta.platform.NetworkManager;
import net.jxta.protocol.PipeAdvertisement;
import net.jxta.socket.JxtaServerSocket;
import ui.ClientUI;
import ui.TimeWorkerListModel;
import application.FileStateAgent;
import application.Client;
import application.MachineIDProvider;
import application.Server;

public class Servent {
    
	private NetworkManager manager = null;
    private DiscoveryService discovery;
    private PeerGroup netPeerGroup = null;
    private PipeAdvertisement pipeAdv;
    private ExecutorService pool;
    private Server server;
    private Client client;
    private File fileRoot;
    public static final String SERVENT_ADVERTISSMENT_NAME = "SDServentAdv";
    private TimeWorkerListModel workers;
    
    public static PipeAdvertisement newServerSocketAdvertisement( PeerGroup netPeerGroup ) {
        PipeID socketID = null;

        socketID = (PipeID) IDFactory.newPipeID( netPeerGroup.getPeerGroupID() );
        PipeAdvertisement advertisement = (PipeAdvertisement)
                AdvertisementFactory.newAdvertisement(PipeAdvertisement.getAdvertisementType());

        advertisement.setPipeID(socketID);
        advertisement.setType(PipeService.UnicastType);
        advertisement.setName(SERVENT_ADVERTISSMENT_NAME);
		//advertisement.setDescription("File Services by " + System.getProperty("user.name") + "@" + InetAddress.getLocalHost().getHostName());
		advertisement.setDescription(ManagementFactory.getRuntimeMXBean().getName());
        	
        return advertisement;
    }

	public Servent( ExecutorService threadPool , File fRoot ){
        try {
        	fileRoot = fRoot;
    		pool = Executors.newCachedThreadPool();
    		
    		manager = new NetworkManager(NetworkManager.ConfigMode.ADHOC,
    				System.getProperty("user.name") + "@" + InetAddress.getLocalHost().getHostName(),
                    new File(new File(".cache"), "Servent").toURI());
            manager.startNetwork();
            
            netPeerGroup = manager.getNetPeerGroup();
            discovery = netPeerGroup.getDiscoveryService();

            //clear pipe advertisements that were in the .cache folder from previous sessions
            Enumeration<Advertisement> eOldAdvertisements = discovery.getLocalAdvertisements(DiscoveryService.ADV, null, null);
            while (eOldAdvertisements.hasMoreElements()) {
                Advertisement oAdvertisement = (Advertisement) eOldAdvertisements.nextElement();
                if (oAdvertisement instanceof PipeAdvertisement) {
                    discovery.flushAdvertisement(oAdvertisement);
                }
            }
            
            pipeAdv = newServerSocketAdvertisement(netPeerGroup);
            
			//TODO DO THIS MORE ACCURATELY
            //will last
            discovery.publish(pipeAdv, DiscoveryService.INFINITE_LIFETIME, 1000L*60L*5L );
			discovery.remotePublish(pipeAdv, 1000L*60L*5L );
			
			// we need now a server thread to handle incoming connections for this advertisment
			// it will be a modified Server() class that accepts the thread pool and other parameters
            JxtaServerSocket serverSocket = new JxtaServerSocket(netPeerGroup , pipeAdv , 10);
            serverSocket.setSoTimeout(0);
            
            workers = new TimeWorkerListModel();
            
			server = new Server( pool, serverSocket, fileRoot,  workers);
			Thread st = new Thread( server, "Servent thread for Server.run()");
			st.start();
			 
			//the machine provider looks for peers on the network constantly and offers a machineID to whoever
			//needs it.
			//
			MachineIDProvider.init(discovery, netPeerGroup , pipeAdv );
			
			//initialize the file state worker singleton class
			FileStateAgent.init(new File(fileRoot,"downloadStates.dat"));
			
			// we also need to create new client workers so that requests are made to the servents 
			// that have published an advertisment. These workers must have access to the 
			// discovery service and be able to distinguish between local and remote advertisements 
			client = new Client(pool, fileRoot, workers );
			
		}catch (Exception e) {
            e.printStackTrace();
            System.exit(-1);
        }
	}

	public Client getClient() {
		return client;
	}

	public static void main(String[] args) {
		
		//do the logger configuration
		Logger logger = Logger.getLogger( Server.loggerName );
		logger.setUseParentHandlers(false);
		logger.setLevel(Level.FINE);
		ConsoleHandler ch = new ConsoleHandler();
		ch.setLevel(Level.FINE);
		logger.addHandler(ch);
		
		File f = null;
		if( args.length >= 1 ){
			f = new File(args[0]);
			if ( !f.exists() ){
				f = null;
			}
		}
		if ( f== null){
			JFileChooser jfc = new JFileChooser();
			jfc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
			jfc.setMultiSelectionEnabled(false);
			jfc.setDialogTitle("Elegir el directorio raiz ");
			if( JFileChooser.APPROVE_OPTION == jfc.showOpenDialog( null ) ){

				f = jfc.getSelectedFile();	
			}
		}
		
		if( f!= null ){
			logger.info("Servent root path is " + f.getPath());

			Servent servent = new Servent(Executors.newCachedThreadPool(), f );

			@SuppressWarnings("unused")
			ClientUI clientUI = new ClientUI( servent );
		}
		
	}

	public TimeWorkerListModel getTimeWorkerListModel() {
		return workers;
	}
}
